﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Drawing;
using System.Xml.Serialization;
using System.Windows.Forms;
using System.Timers;
using Microsoft.Win32;

namespace 社畜
{

	public enum 計算タイプ
	{
		分散タイプ,
		上限指定タイプ,		
	};

	public class 社畜API
	{

		const double OverTimeMax = 14;
	
		/// <summary>コンストラクタ</summary>
		public 社畜API()
		{
			this.QuataTime = 0;
			this.CurrentOverTime = 0;
			this.PaidHolidayList = new List<DateTime>();
			this.OffDayList = new List<DateTime>();
			this.OnTimeDayList = new List<DateTime>();
			this.ScheduleMonthList = new List<DateTime>();
			this.TotalOverWorkTime = "";
			this.workDay = 0;
			this.CalcType = 計算タイプ.分散タイプ;
			this.LimitTime = 0.0;
			this.OverWorkDayList = new List<残業日いんたーふぇーす>();
			this.ResultWorkDayList = new List<残業日いんたーふぇーす>();

			//イベントをイベントハンドラに関連付ける
			SystemEvents.SessionEnding += new SessionEndingEventHandler(this.SystemEvents_SessionEnding);
		}

		/// <summary>デストラクタ</summary>
		~社畜API()
		{
			//イベントを開放する
			SystemEvents.SessionEnding -=	new SessionEndingEventHandler(this.SystemEvents_SessionEnding);	
		}

		#region パブリックメソッド
		/// <summary>
		/// 残業時間計算
		/// </summary>
		/// <param name="OverWorkDays">残業日データクラスリスト</param>
		/// <returns></returns>
		public void OverTimeCalculation()
		{
			List<残業日いんたーふぇーす> OverWorkDays = new List<残業日いんたーふぇーす>();

			// 有給日数
			int PaidHolidayCount = this.PaidHolidayList.Count;

			if (this.QuataTime + 8 * PaidHolidayCount - this.CurrentOverTime < 0)
			{
				throw new Exception("もういい・・・もう休め・・・\n\nノルマ達成してますよ");
			}
			
			TimeSpan spanDay = endDay - startDay;

			// その月の日数(月途中の場合は残り実日数)
			int totalDay = spanDay.Days;

			// 稼働日数(月途中の場合は残り実稼働日数)
			this.workDay = totalDay - this.OffDayList.Count;

			// 残り残業時間
			double restOverWorkTime = this.QuataTime + 8 * PaidHolidayCount - this.CurrentOverTime;

			// 合計残業時間文字列
			this.TotalOverWorkTime = restOverWorkTime.ToString("f1");

			// 残業可能日数 = 稼働日数 ー 有給日数 ー 定退日日数
			int overWorkDayCount = this.workDay - PaidHolidayCount - this.OnTimeDayList.Count;

			if (overWorkDayCount <= 0)
			{
				throw new Exception("残業できる日がありません");
			}

			// 分散方式のとき
			if (this.CalcType == 計算タイプ.分散タイプ)
			{
				// 分散計算
				DispersalCalculation(overWorkDayCount, restOverWorkTime, ref OverWorkDays);	
			}
			// 上限設定タイプ
			else if (this.CalcType == 計算タイプ.上限指定タイプ)
			{
				// 上限計算
				LimitCalculation(overWorkDayCount, restOverWorkTime, ref OverWorkDays);		
			}

			// 保存
			SaveFile(OverWorkDays);

			this.OverWorkDayList = OverWorkDays;

			// 終了時刻のタイマーを設定
			setTimer(this.OverWorkDayList);

			return;
		}

		/// <summary>
		/// 残業時間調整
		/// </summary>
		/// <param name="adjustedOverWorkDays">[in,out]残業日データクラスリスト</param>
		/// <returns></returns>
		public void OverTimeAdjust()
		{
			List<残業日いんたーふぇーす> adjustedOverWorkDays = new List<残業日いんたーふぇーす>();
			adjustedOverWorkDays = this.OverWorkDayList;

			// 合計残業時間
			double adjustOverWorkTime = 0.0;

			foreach( OverWorkDay overWorkDay in adjustedOverWorkDays)
			{
				// 残業時間を調整した日かを確認
				if (overWorkDay.adjustOverWorkTime - overWorkDay.overWorkTime != 0 || overWorkDay.fixedDay == true)
				{
					// 時間固定フラグをtrueに
					overWorkDay.fixedDay = true;

					// 合計残業時間から減算
					adjustOverWorkTime -= (overWorkDay.adjustOverWorkTime - overWorkDay.overWorkTime);
				}
				else
				{
					// 合計残業時間に加算
					adjustOverWorkTime += overWorkDay.overWorkTime;
				}
			}

			// 埋め合わせ不要
			if (adjustOverWorkTime < 0)
			{
				throw new Exception("埋め合わせ不要です");
			}

			// 分散方式のとき
			if (this.CalcType == 計算タイプ.分散タイプ)
			{
				DispersalAdjust(adjustOverWorkTime, ref adjustedOverWorkDays);
			}
			// 上限設定タイプ
			else if (this.CalcType == 計算タイプ.上限指定タイプ)
			{
				LimitAdjust(adjustOverWorkTime, ref adjustedOverWorkDays);
			}

			this.OverWorkDayList = adjustedOverWorkDays;

			// 保存
			SaveFile(adjustedOverWorkDays);

			// 終了時刻のタイマーを設定
			setTimer(this.OverWorkDayList);

			return;
		}

		/// <summary>
		/// 終了時間の通知
		/// </summary>
		public void PopupEnd(object sender, ElapsedEventArgs e)
		{
			MessageBox.Show("終了時間ですよ", "通知", MessageBoxButtons.OK, MessageBoxIcon.Information);
		}
		#endregion

		#region プライベートメソッド
		/// <summary>
		/// 休業日カレンダーから休業日情報を取得
		/// </summary>
		private void GetCalender()
		{
			// 設定できるかのフラグがfalseなら例外を飛ばす
			if (canSetScheduleMonth == false)
			{
				// 例外を飛ばす
				throw new Exception("まだカレンダー読み込み中っす");
			}

			// 設定できない状態にする
			canSetScheduleMonth = false;

			// 21日を超えている場合は翌月1日にscheduleMonthを変更
			if (21 <= this.scheduleMonth.Day)
			{
				this.scheduleMonth = this.scheduleMonth.AddDays(-(this.scheduleMonth.Day) + 1);
				this.scheduleMonth = this.scheduleMonth.AddMonths(1);
			}

			DateTime preMonth = this.ScheduleMonth.AddMonths(-1);
			
			// 開始日を設定(基本は21日)
			DateTime start = new DateTime(preMonth.Year, preMonth.Month, 21);

			// 開始日を過ぎている場合は今日を開始日にする
			if (start <= today)
			{
				startDay = today;
			}
			else
			{
				startDay = start;
			}
			DateTime end = new DateTime(this.ScheduleMonth.Year, this.ScheduleMonth.Month, 21);
			endDay = end;

			string startEnd = null;
			startEnd = "start-min=" + startDay.Year.ToString() + "-" + startDay.Month.ToString("00")
				+ "-" + startDay.Day.ToString("00") + "&start-max=" + this.ScheduleMonth.Year.ToString() + "-" + this.ScheduleMonth.Month.ToString("00")
			+ "-21";

			DateTime tempStart = startDay;
			DateTime tempEnd = endDay;
			this.ScheduleMonthList.Clear();
			// 全ての日を入れる
			for (; tempStart.Date < tempEnd.Date; tempStart = tempStart.AddDays(1))
			{
				this.ScheduleMonthList.Add(new DateTime(tempStart.Year, tempStart.Month, tempStart.Day));
			}

			string URL =
"http://www.google.com/calendar/feeds/id.amada.biz_e99dmr50cm0vd7dod3g618jars%40group.calendar.google.com/private-5e15ace8f56cbac200964dfe8f0745a8/full?" + startEnd;

			XDocument doc = XDocument.Load(URL);

			string ATOM = "{http://www.w3.org/2005/Atom}";
			string GOOGLE = "{http://schemas.google.com/g/2005}"; 
			
			var queryStart = from entry in doc.Root.Elements(ATOM + "entry")
							 from when in entry.Elements(GOOGLE + "when")
							 orderby when.Attribute("startTime").Value
							 select new
							 {
								 Title = entry.Element(ATOM + "title").Value,
								 Date = when.Attribute("startTime").Value
							 };
			var queryEnd = from entry in doc.Root.Elements(ATOM + "entry")
						   from when in entry.Elements(GOOGLE + "when")
						   orderby when.Attribute("endTime").Value
						   select new
						   {
							   Title = entry.Element(ATOM + "title").Value,
							   Date = when.Attribute("endTime").Value
						   };

			int startCount = queryStart.Count();
			int endCount = queryEnd.Count();

			if (startCount != endCount)
			{
				return;
			}

			int i = 0;
			// 休業日を数える
			this.OffDayList.Clear();
			for (i = 0; i < startCount; i++)
			{
				tempStart = DateTime.Parse(queryStart.ElementAt(i).Date);
				tempEnd = DateTime.Parse(queryEnd.ElementAt(i).Date);

				// 休業日をリストに入れる
				for (; tempStart.Date < tempEnd.Date; tempStart = tempStart.AddDays(1))
				{
					DateTime temp = new DateTime(tempStart.Year, tempStart.Month, tempStart.Day);
					if (startDay < temp && temp < endDay )
					{
						this.OffDayList.Add(temp);
					}
				}
			}

			// 設定できる状態にする
			canSetScheduleMonth = true;
		}

		/// <summary>
		/// 分散型計算
		/// </summary>
		/// <param name="overWorkDayCount">残業日数</param>
		/// <param name="restOverWorkTime">残り残業時間</param>
		/// <param name="OverWorkDays">残業日データクラスリスト</param>
		/// <returns>成否</returns>
		private void DispersalCalculation(int overWorkDayCount, double restOverWorkTime, ref List<残業日いんたーふぇーす> OverWorkDays)
		{
			// 残業時間少ない方
			double MinOverTime = 0.0;
			// 残業時間多い方
			double MaxOverTime = 0.5;

			int i = 0;
			// 最適な残業時間の組み合わせを探索
			for (i = 0; i <= overWorkDayCount; i++)
			{
				if (restOverWorkTime - ((MinOverTime * i) + (MaxOverTime * (overWorkDayCount - i))) == 0)
				{
					break;
				}

				// 組み合わせが見つからなければ残業時間を増やして再探索
				if (i == overWorkDayCount)
				{
					i = 0;
					MinOverTime = MaxOverTime;
					MaxOverTime += 0.5;
				}
			}

			int MinOverCount = i;
			int MaxOverCount = overWorkDayCount - i;

			DateTime tempStart = startDay;
			DateTime tempEnd = endDay;
			// 残業予定リストに残業する日を入れる	
			for (; tempStart.Date < tempEnd.Date; tempStart = tempStart.AddDays(1))
			{
				// 残業可能日かを調べる
				if (IsEnableOverWork(tempStart.Date) == false)
				{
					// ダメなら次の日
					continue;
				}

				// DateTimeを作成
				DateTime date = new DateTime(tempStart.Year, tempStart.Month, tempStart.Day);
				OverWorkDay temp = new OverWorkDay();
				temp.date = date;
				// 残業時間分時間を進める
				if (0 < MaxOverCount)
				{
					if (OverTimeMax < MaxOverTime)
					{
						throw new Exception("翌朝8時30分まで働いてもノルマ達成できません");
					}
					temp.overWorkTime = MaxOverTime;
					temp.adjustOverWorkTime = MaxOverTime;
					MaxOverCount--;
				}
				else
				{
					if (OverTimeMax < MinOverTime)
					{
						throw new Exception("翌朝8時30分まで働いてもノルマ達成できません");
					}

					temp.overWorkTime = MinOverTime;
					temp.adjustOverWorkTime = MinOverTime;
				}

				OverWorkDays.Add(temp);
			}
			
			return;
		}

		/// <summary>
		/// 上限指定型計算
		/// </summary>
		/// <param name="overWorkDayCount">残業日数</param>
		/// <param name="restOverWorkTime">残り残業時間</param>
		/// <param name="OverWorkDays">残業日データクラスリスト</param>
		/// <returns>成否</returns>
		private void LimitCalculation(int overWorkDayCount, double restOverWorkTime, ref List<残業日いんたーふぇーす> OverWorkDays)
		{
			// 残業可能日に全て上限まで残業してもノルマ消化できない場合は失敗
			if (this.LimitTime * overWorkDayCount < restOverWorkTime)
			{
				throw new Exception("上限が少ないです\n毎日上限まで残業してもノルマ達成できません");
			}

			DateTime tempStart = startDay;
			DateTime tempEnd = endDay;
			// 残業予定リストに残業する日を入れる	
			for (; tempStart.Date < tempEnd.Date; tempStart = tempStart.AddDays(1))
			{
				// 残業可能日かを調べる
				if (IsEnableOverWork(tempStart.Date) == false)
				{
					// ダメなら次の日
					continue;
				}

				// DateTimeを作成
				DateTime date = new DateTime(tempStart.Year, tempStart.Month, tempStart.Day);
				OverWorkDay temp = new OverWorkDay();
				temp.date = date;

				// 残りノルマ時間が上限以上のとき
				if (this.LimitTime <= restOverWorkTime)
				{
					temp.overWorkTime = this.LimitTime;
					temp.adjustOverWorkTime = this.LimitTime;
					restOverWorkTime -= this.LimitTime;
				}
				// 残りノルマ時間が上限未満のとき
				else
				{
					temp.overWorkTime = restOverWorkTime;
					temp.adjustOverWorkTime = restOverWorkTime;
					restOverWorkTime = 0;
				}

				OverWorkDays.Add(temp);
			}

			return;
		}

		/// <summary>
		/// 残業可能な日かを取得
		/// </summary>
		/// <param name="day">調べる日</param>
		/// <returns>true:残業可能日 false:残業不可能日</returns>
		private bool IsEnableOverWork( DateTime day )
		{
			// 有給日か調べる
			foreach (DateTime paid in this.PaidHolidayList)
			{
				if (paid.Date == day.Date)
				{
					// 有給日なので残業不可能
					return false;
				}
			}

			// 休業日か調べる
			foreach (DateTime off in OffDayList)
			{
				if (off.Date == day.Date)
				{
					// 休業日なので残業不可能
					return false;
				}
			}

			// 定時退社日か調べる
			foreach (DateTime onTime in this.OnTimeDayList)
			{
				if (onTime.Date == day.Date)
				{
					// 定時退社日なので残業不可能
					return false;
				}
			} 
			
			// どれにも当てはまらないので残業可能
			return true;
		}

		/// <summary>
		/// 分散型補正計算
		/// </summary>
		/// <param name="overWorkTime">残業時間</param>
		/// <param name="adjustOverWorkDays">調整日リスト</param>
		/// <returns>成否</returns>
		private void DispersalAdjust(double overWorkTime, ref List<残業日いんたーふぇーす> adjustOverWorkDays)
		{
			// 調整対象の日数
			int totalAdjustDayCount = adjustOverWorkDays.Count;
			foreach (OverWorkDay overWorkDay in adjustOverWorkDays)
			{
				if (overWorkDay.fixedDay == true)
				{
					totalAdjustDayCount--;
				}
			}

			// 調整可能日がない
			if (totalAdjustDayCount <= 0)
			{
				throw new Exception("調整可能日がないです\n一度計算しなおしてください");
			}

			// 残業時間少ない方
			double MinOverTime = 0.0;
			// 残業時間多い方
			double MaxOverTime = 0.5;

			int i = 0;
			// 最適な残業時間の組み合わせを探索
			for (i = 0; i <= totalAdjustDayCount; i++)
			{
				if (overWorkTime - ((MinOverTime * i) + (MaxOverTime * (totalAdjustDayCount - i))) == 0)
				{
					break;
				}

				// 組み合わせが見つからなければ残業時間を増やして再探索
				if (i== totalAdjustDayCount)
				{
					i = 0;
					MinOverTime = MaxOverTime;
					MaxOverTime += 0.5;
				}
			}

			int MinOverCount = i;
			int MaxOverCount = totalAdjustDayCount - i;

			foreach (OverWorkDay overWorkDay in adjustOverWorkDays)
			{
				// 調整対象じゃない日は次の日へ
				if (overWorkDay.fixedDay == true)
				{
					overWorkDay.overWorkTime = overWorkDay.adjustOverWorkTime;
					continue;
				}
				
				// 残業時間分時間を振り分ける
				if (0 < MaxOverCount)
				{
					if (OverTimeMax < MaxOverTime)
					{
						throw new Exception("翌朝8時30分まで働いてもノルマ達成できません");
					}

					overWorkDay.overWorkTime = MaxOverTime;
					overWorkDay.adjustOverWorkTime = MaxOverTime;
					MaxOverCount--;
				}
				else
				{
					if (OverTimeMax < MinOverTime)
					{
						throw new Exception("翌朝8時30分まで働いてもノルマ達成できません");
					}

					overWorkDay.overWorkTime = MinOverTime;
					overWorkDay.adjustOverWorkTime = MinOverTime;
				}
			}

			// 固定日フラグを落とす
		/*	foreach (OverWorkDay overWorkDay in adjustOverWorkDays)
			{
				overWorkDay.fixedDay = false;
			}*/

			return;
		}

		/// <summary>
		/// 上限型補正計算
		/// </summary>
		/// <param name="overWorkTime">残業時間</param>
		/// <param name="adjustOverWorkDays">調整日リスト</param>
		/// <returns></returns>
		private void LimitAdjust(double overWorkTime, ref List<残業日いんたーふぇーす> adjustOverWorkDays)
		{
			// 調整対象の日数
			int totalAdjustDayCount = adjustOverWorkDays.Count;
			foreach (OverWorkDay overWorkDay in adjustOverWorkDays)
			{
				if (overWorkDay.fixedDay == true)
				{
					totalAdjustDayCount--;
				}
			}

			// 調整可能日がない
			if (totalAdjustDayCount <= 0)
			{
				throw new Exception("調整可能日がないです\n一度計算しなおしてください");
			}

			// 残業可能日に全て上限まで残業してもノルマ消化できない場合は失敗
			if (this.LimitTime * totalAdjustDayCount < overWorkTime)
			{
				throw new Exception("上限が少ないです\n毎日上限まで残業してもノルマ達成できません");
			}

			foreach (OverWorkDay overWorkDay in adjustOverWorkDays)
			{
				// 調整対象じゃない日は次の日へ
				if (overWorkDay.fixedDay == true)
				{
					overWorkDay.overWorkTime = overWorkDay.adjustOverWorkTime;
					continue;
				}
				
				// 残りノルマ時間が上限以上のとき
				if (this.LimitTime <= overWorkTime)
				{
					overWorkDay.overWorkTime = this.LimitTime;
					overWorkDay.adjustOverWorkTime = this.LimitTime;
					overWorkTime -= this.LimitTime;
				}
				// 残りノルマ時間が上限未満のとき
				else
				{
					overWorkDay.overWorkTime = overWorkTime;
					overWorkDay.adjustOverWorkTime = overWorkTime;
					overWorkTime = 0;
				}
			}

			// 固定日フラグを落とす
			foreach (OverWorkDay overWorkDay in adjustOverWorkDays)
			{
				overWorkDay.fixedDay = false;
			}

			return;
		}
	
		/// <summary>
		/// 保存
		/// </summary>
		/// <param name="OverWorkDays">残業日データクラス</param>
		private void SaveFile(List<残業日いんたーふぇーす> OverWorkDays)
		{
			//保存先のファイル名
			string fileDate = this.scheduleMonth.Year.ToString() + @"年" + this.scheduleMonth.Month.ToString("d2") + @"月";
			string fileName = fileDate + @".社畜";

			// 保存情報クラス
			社畜情報 saveData = new 社畜情報();

			// 保存情報格納
			saveData.QuataTime = this.QuataTime;
			saveData.CurrentOverTime = this.CurrentOverTime;
			saveData.OnTimeDayList = this.OnTimeDayList;
			saveData.PaidHolidayList = this.PaidHolidayList;
			saveData.TotalOverWorkTime = this.TotalOverWorkTime;
			saveData.CalcType = this.CalcType;
			saveData.LimitTime = this.LimitTime;
			saveData.workDay = this.workDay;
			foreach (OverWorkDay obj in OverWorkDays)
			{
				saveData.OverWorkDayList.Add(obj);
			}
			foreach (OverWorkDay obj in ResultWorkDayList)
			{
				saveData.ResultWorkDayList.Add(obj);
			}

			//XmlSerializerオブジェクトを作成
			//オブジェクトの型を指定する
			System.Xml.Serialization.XmlSerializer serializer =
				new System.Xml.Serialization.XmlSerializer(typeof(社畜情報));
			//書き込むファイルを開く
			System.IO.FileStream fs = new System.IO.FileStream(
				fileName, System.IO.FileMode.Create);
			
			//シリアル化し、XMLファイルに保存する
			serializer.Serialize(fs, saveData);
			
			//ファイルを閉じる
			fs.Close();
		}

		/// <summary>
		/// ファイル読み込み
		/// </summary>
		/// <param name="filePath">ファイルパス</param>
		private void LoadFile(string filePath)
		{
			// 残業日リストを空にする
			this.OverWorkDayList.Clear();
			this.QuataTime = 30;
			this.CurrentOverTime = 0;
			this.OnTimeDayList.Clear();
			this.PaidHolidayList.Clear();
			this.TotalOverWorkTime = "";
			this.CalcType = 計算タイプ.分散タイプ;
			this.LimitTime = 0;
			this.workDay = 0;

			// ファイルが無ければ終了
			if (false == System.IO.File.Exists(filePath))
			{
				return;
			}

			//XmlSerializerオブジェクトを作成
			System.Xml.Serialization.XmlSerializer serializer =
				new System.Xml.Serialization.XmlSerializer(typeof(社畜情報));
			//読み込むファイルを開く
			System.IO.FileStream fs = new System.IO.FileStream(
				filePath, System.IO.FileMode.Open);

			//XMLファイルから読み込み、逆シリアル化する
			社畜情報 saveData = (社畜情報)serializer.Deserialize(fs);

			// 保存情報をプロパティへ
			this.QuataTime = saveData.QuataTime;
			this.CurrentOverTime = saveData.CurrentOverTime;
			//this.OnTimeDayList = saveData.OnTimeDayList;
			//this.PaidHolidayList = saveData.PaidHolidayList;
			this.CalcType = saveData.CalcType;
			this.LimitTime = saveData.LimitTime;
			this.workDay = saveData.workDay;

			// 定退日情報をクリア
			this.OnTimeDayList.Clear();

			// 定退日情報をクリア
			foreach (DateTime load in saveData.OnTimeDayList)
			{
				// 今日以降の情報のとき
				if ( DateTime.Now.Date <= load.Date )
				{
					// 定退日情報に設定
					this.OnTimeDayList.Add(load);
				}
			}

			// 有給日情報をクリア
			this.PaidHolidayList.Clear();

			// 有給日情報をクリア
			foreach (DateTime load in saveData.PaidHolidayList)
			{
				// 今日以降の情報のとき
				if (DateTime.Now.Date <= load.Date)
				{
					// 有給日情報に設定
					this.PaidHolidayList.Add(load);
				}
			}

			// いるかわからんが実績情報
			foreach (OverWorkDay load in saveData.ResultWorkDayList)
			{
				this.ResultWorkDayList.Add(load);
			}

			// ファイルに保存されている合計残業時間
			double resultOverWork = double.Parse(saveData.TotalOverWorkTime);
			foreach (OverWorkDay load in saveData.OverWorkDayList)
			{
				// 今日より前の情報のとき
				if (load.date < DateTime.Now.Date)
				{
					// 残業時間を合計から差し引く
					resultOverWork -= load.overWorkTime;
					// 超過時間に加算
					this.CurrentOverTime += load.overWorkTime;
					// 実績リストに追加
					this.ResultWorkDayList.Add(load);
					// 稼働日数をデクリメント
					this.workDay--;
				}
				// 今日以降のものはリストに追加
				else
				{
					this.OverWorkDayList.Add(load);
				}
			}

			// 合計残業時間を文字列に
			this.TotalOverWorkTime = resultOverWork.ToString("f1");

			//ファイルを閉じる
			fs.Close();

			// 終了時刻のタイマーを設定
			setTimer(this.OverWorkDayList);
		}

		/// <summary>
		/// ポップアップ通知用のタイマーを設定する
		/// </summary>
		/// <param name="OverWorkDays">残業日リスト</param>
		/// <remarks>リストに今日の情報があればタイマーを設定、なければ何もしない</remarks>
		private void setTimer(List<残業日いんたーふぇーす> OverWorkDays)
		{
			// 今の時間
			DateTime nowTime = DateTime.Now;
			// タイマーを更新するかのフラグ
			bool updateTimer = false;
			// 今日の残業情報取得用
			OverWorkDay todayOverWork = new OverWorkDay();

			foreach (残業日いんたーふぇーす いんたーふぇーす in OverWorkDays)
			{
				// 今日と一致する日付があるかを探索
				if (いんたーふぇーす.GetDate().Date == nowTime.Date)
				{
					todayOverWork = (OverWorkDay)いんたーふぇーす;
					updateTimer = true;
					break;
				}
			}

			// 今日が見つからなければタイマーを更新せずに終了
			if (updateTimer == false)
			{
				return;
			}

			// 終業時刻を計算
			DateTime endTime = todayOverWork.date.AddHours(17.5 + todayOverWork.overWorkTime);

			// 残り時間をミリ秒で取得
			double remainTimeMS = (endTime - nowTime).TotalMilliseconds;

			// もう時間を過ぎているときはタイマー更新せずにメッセージボックスを出して終了
			if (remainTimeMS < 0)
			{
				//MessageBox.Show("もうすでに終了時間ですよ！", "通知", MessageBoxButtons.OK, MessageBoxIcon.Information);
				return;
			}

			// 一度計算していたかもしれないので破棄して再生成する
			popUpTimer.Dispose();
			popUpTimer = new System.Timers.Timer();

			// PopupEndメソッドを渡す
			popUpTimer.Elapsed += new ElapsedEventHandler(this.PopupEnd);
			// 今から待機する時間
			popUpTimer.Interval = remainTimeMS;
			// 繰り返しはしない
			popUpTimer.AutoReset = false;
			// タイマー開始
			popUpTimer.Enabled = true;
		}


		/// <summary>
		/// ログオフ、シャットダウンしようとしているときの処理
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void SystemEvents_SessionEnding(object sender, SessionEndingEventArgs e)
		{
			// 今の時間を取得
			DateTime shutdownTime = DateTime.Now;

			// 18時前なら何もしない
			if( shutdownTime < new DateTime(shutdownTime.Year,shutdownTime.Month,shutdownTime.Day,18,0,0))
			{
				return;
			}

			// 今月の情報を読む
			string FileName = "";
			if (21 <= shutdownTime.Day)
			{
				FileName = shutdownTime.Year.ToString() + @"年" + (shutdownTime.Month + 1).ToString("d2") + @"月.社畜";
			}
			else
			{
				FileName = shutdownTime.Year.ToString() + @"年" + shutdownTime.Month.ToString("d2") + @"月.社畜";
			}

			this.LoadFile(FileName);

			// 終業時刻探索
			DateTime compareTime = new DateTime(shutdownTime.Year,shutdownTime.Month,shutdownTime.Day,18,0,0);
			while (compareTime <= shutdownTime)
			{
				compareTime = compareTime.AddHours(0.5);
			}

			// 超えた時間を探したので30分戻した時間が終業時刻
			DateTime endTime = compareTime.AddHours(-0.5);

			// 今日の残業時間を計算(終業時刻-17時30分)
			double todayOverWork = (endTime - new DateTime(shutdownTime.Year, shutdownTime.Month, shutdownTime.Day, 17, 30, 0)).TotalHours;

			// 残業予定との差分
			double diffOverWork = 0;

			// 今日の情報に保存する
			foreach( OverWorkDay today in this.OverWorkDayList)
			{
				if( today.date == DateTime.Now.Date )
				{
					// 残業予定との差分
					diffOverWork = todayOverWork - today.overWorkTime;
					// 予定を実績の時間に置き換え
					today.overWorkTime = todayOverWork;
					break;
				}
			}

			// 残業予定との差分が0のとき
			if (diffOverWork == 0)
			{
				// 定退日に残業している可能性があるので探索
				// 流石に有給日のフォローはしない

				int i = 0;
				for ( i = 0; i < this.OnTimeDayList.Count; i++ )
				{
					// 定退日に残業していたとき
					if (this.OnTimeDayList[i].Date == DateTime.Now.Date)
					{
						// 定退日リストから今日を削除
						this.OnTimeDayList.RemoveAt(i);

						// 残業日リストに今日を追加
						OverWorkDay today = new OverWorkDay();
						today.date = DateTime.Now.Date;
						today.overWorkTime = todayOverWork;
						today.adjustOverWorkTime = todayOverWork;
						this.OverWorkDayList.Add(today);
						diffOverWork = todayOverWork;
						break;
					}
				}
			}			
			
			if (0 < diffOverWork)
			{
				while (0 < diffOverWork)
				{
					// 差分を埋め合わせ
					foreach (OverWorkDay adjustDay in this.OverWorkDayList)
					{
						if (diffOverWork <= 0)
						{
							break;
						}

						// 今日は埋め合わせしない
						if (adjustDay.date != DateTime.Now.Date)
						{
							adjustDay.overWorkTime -= 0.5;
							diffOverWork -= 0.5;
						}
					}
				}
			}
			else if (diffOverWork < 0)
			{
				while (diffOverWork < 0)
				{
					// 差分を埋め合わせ
					foreach (OverWorkDay adjustDay in this.OverWorkDayList)
					{
						if (0 <= diffOverWork)
						{
							break;
						}

						// 今日は埋め合わせしない
						if (adjustDay.date != DateTime.Now.Date)
						{
							adjustDay.overWorkTime += 0.5;
							diffOverWork += 0.5;
						}
					}
				}
			}

			// 保存
			this.SaveFile(this.OverWorkDayList);

			// アプリ終了	
		}
		#endregion

		#region プロパティ
		/// <summary>
		/// ノルマ時間。必須(0以上)
		/// </summary>
		/// <remarks></remarks>
		public double QuataTime { set; get; }

		/// <summary>
		/// 現在の超過時間。必須
		/// </summary>
		public double CurrentOverTime { set; get; }

		/// <summary>
		/// 有給日のリスト。必須
		/// </summary>
		public List<DateTime> PaidHolidayList { set; get; }

		/// <summary>
		/// 休業日のリスト
		/// </summary>
		/// <remarks>予定月決定のタイミングで値が入る</remarks>
		public List<DateTime> OffDayList { private set; get; }

		/// <summary>
		/// 定時退社日のリスト
		/// </summary>
		public List<DateTime> OnTimeDayList { set; get; }


		/// <summary>
		/// 予定を立てる月。超必須
		/// </summary>
		private DateTime scheduleMonth = new DateTime();
		public DateTime ScheduleMonth
		{ 
			set
			{
				scheduleMonth = value;
				GetCalender();

				string loadFile = ScheduleMonth.Year.ToString() + @"年" + ScheduleMonth.Month.ToString("d2") + @"月.社畜";

				// 読み込んでみる
				LoadFile(loadFile);
			}
			get
			{
				return scheduleMonth;
			}
		}

		/// <summary>
		/// 稼働日数
		/// </summary>
		public int workDay { private set; get; }

		/// <summary>
		/// 合計残業時間(文字列)
		/// </summary>
		public String TotalOverWorkTime { private set; get; }

		/// <summary>
		/// 予定月の日全てのリスト、当月の場合は残りの日全てのリスト
		/// </summary>
		/// <remarks>予定月決定のタイミングで値が入る</remarks>
		public List<DateTime> ScheduleMonthList { private set; get; }

		/// <summary>
		/// 計算タイプ
		/// </summary>
		/// <remarks>0:分散タイプ 1:上限設定タイプ</remarks>
		public 計算タイプ CalcType { set; get; }

		/// <summary>
		/// 上限残業時間
		/// </summary>
		/// <remarks>上限設定タイプのときの上限残業時間</remarks>
		public double LimitTime { set; get; }

		/// <summary>
		/// 残業日のリスト
		/// </summary>
		public List<残業日いんたーふぇーす> OverWorkDayList{set;get;}

		/// <summary>
		/// 残業日のリスト
		/// </summary>
		public List<残業日いんたーふぇーす> ResultWorkDayList { set; get; }

		#endregion

		#region フィールド
		/// <summary>
		/// 今日
		/// </summary>
		private DateTime today = new DateTime(DateTime.Now.Year,DateTime.Now.Month,DateTime.Now.Day);

		/// <summary>
		/// 開始日
		/// </summary>
		private DateTime startDay;

		/// <summary>
		/// 締め日
		/// </summary>
		private DateTime endDay;	
		
		/// <summary>
		/// ScheduleMonthに設定できるかのフラグ
		/// </summary>
		private bool canSetScheduleMonth = true;

		/// <summary>
		/// ポップアップ通知用のタイマー
		/// </summary>
		private System.Timers.Timer popUpTimer = new System.Timers.Timer();

		#endregion

	}
	public interface 残業日いんたーふぇーす
	{
		DateTime GetDate();
		String GetEndTime();
		String GetOverWorkTime();

		double adjustOverWorkTime { set; get; }
	}

	/// <summary>
	/// 残業日データクラス
	/// </summary>
	public class OverWorkDay : 残業日いんたーふぇーす
	{
		/// <summary>
		/// コンストラクタ
		/// </summary>
		public OverWorkDay()
		{
			adjustOverWorkTime = 0.0;
		}

		#region パブリックメソッド
		/// <summary>
		/// 日付取得
		/// </summary>
		/// <returns></returns>
		public DateTime GetDate()
		{
			return date;
		}

		/// <summary>
		/// 終業時刻取得
		/// </summary>
		/// <returns></returns>
		public String GetEndTime()
		{
			DateTime startTime = new DateTime( date.Year,date.Month,date.Day );
			DateTime endTime = startTime.AddHours(17.5 + overWorkTime);

			TimeSpan end = endTime - startTime;

			String retTemp = ( end.Days * 24 +  end.Hours).ToString() + ":" + end.Minutes.ToString("00");
			return retTemp;
		}

		/// <summary>
		/// 残業時間取得
		/// </summary>
		/// <returns></returns>
		public String GetOverWorkTime()
		{
			return overWorkTime.ToString( "f1" );
		}
		#endregion

		#region プロパティ
		/// <summary>
		/// 調整後の残業時間
		/// </summary>
		public double adjustOverWorkTime{set;get;}
		#endregion


		#region フィールド
		/// <summary>
		/// 日付
		/// </summary>
		public DateTime date{set; get;}

		/// <summary>
		/// 残業時間
		/// </summary>
		public double overWorkTime { set; get; }

		/// <summary>
		/// 残業時間が固定の日
		/// </summary>
		public bool fixedDay = false;
		#endregion		
	}


	/// <summary>	保存データクラス</summary>
	public class 社畜情報
	{
		/// <summary>ノルマ時間</summary>
		public double QuataTime = 0;
		/// <summary>超過時間</summary>
		public double CurrentOverTime = 0;
		/// <summary>予定超過時間リスト</summary>
		public List<OverWorkDay> OverWorkDayList = new List<OverWorkDay>();
		/// <summary>有給日リスト</summary>
		public List<DateTime> PaidHolidayList = new List<DateTime>();
		/// <summary>定退日リスト</summary>
		public List<DateTime> OnTimeDayList = new List<DateTime>();
		/// <summary>合計残業時間</summary>
		public string TotalOverWorkTime = "";
		/// <summary>計算タイプ</summary>
		public 計算タイプ CalcType = 計算タイプ.分散タイプ;
		/// <summary>上限時間</summary>
		public double LimitTime = 0;
		/// <summary>実績リスト</summary>
		public List<OverWorkDay> ResultWorkDayList = new List<OverWorkDay>();
		/// <summary>稼働日数</summary>
		public int workDay = new int();
	}
}